#include "burner.h"
#include "cheat.h"
#include "../../lib/kaillera/kailleraclient.h"

const int MAXPLAYER = 4;
static int playerInputs[MAXPLAYER], commonInputs, DIPInputs;
static int playerOffset[MAXPLAYER], commonOffset, DIPOffset;

const int INPUTSIZE = 8 * (4 + 8);
static unsigned char controls[INPUTSIZE];

int kNetGame = 0;					// Non-zero if Kaillera is being used

// Inputs are assumed to be in the following order:
// All player 1 controls
// All player 2 controls (if any)
// All player 3 controls (if any)
// All player 4 controls (if any)
// All common controls
// All DIP switches

int kailleraInitInput()
{
	if (gameInpCount == 0) {
		return 1;
	}

	BurnInputInfo bii;
	memset(&bii, 0, sizeof(bii));

	unsigned int i = 0;

	playerOffset[0] = 0;
	do {
		BurnDrvGetInputInfo(&bii, i);
		i++;
	} while (!_strnicmp(bii.szName, "P1", 2) && i <= gameInpCount);
	i--;
	playerInputs[0] = i - playerOffset[0];

	for (int j = 1; j < MAXPLAYER; j++) {
		char szString[3] = "P?";
		szString[1] = j + '1';
		playerOffset[j] = i;
		while (!_strnicmp(bii.szName, szString, 2) && i < gameInpCount) {
			i++;
			BurnDrvGetInputInfo(&bii, i);
		}
		playerInputs[j] = i - playerOffset[j];
	}

	commonOffset = i;
	while ((bii.nType & BIT_GROUP_CONSTANT) == 0 && i < gameInpCount){
		i++;
		BurnDrvGetInputInfo(&bii, i);
	};
	commonInputs = i - commonOffset;

	DIPOffset = i;
	DIPInputs = gameInpCount - DIPOffset;

	return 0;
}

int kailleraGetInput()
{
	static int i, j, k;

	static BurnInputInfo bii;
	memset(&bii, 0, sizeof(bii));

	// Initialize controls to 0
	memset(controls, 0, INPUTSIZE);

	// Pack all DIP switches + common controls + player 1 controls
	for (i = 0, j = 0; i < playerInputs[0]; i++, j++) {
		BurnDrvGetInputInfo(&bii, i + playerOffset[0]);
		if (*bii.pVal && bii.nType == BIT_DIGITAL) {
			controls[j >> 3] |= (1 << (j & 7));
		}
	}
	for (i = 0; i < commonInputs; i++, j++) {
		BurnDrvGetInputInfo(&bii, i + commonOffset);
		if (*bii.pVal) {
			controls[j >> 3] |= (1 << (j & 7));
		}
	}

	// Convert j to byte count
	j = (j + 7) >> 3;

	// Analog controls/constants
	for (i = 0; i < playerInputs[0]; i++) {
		BurnDrvGetInputInfo(&bii, i + playerOffset[0]);
		if (*bii.pVal && bii.nType != BIT_DIGITAL) {
			if (bii.nType & BIT_GROUP_ANALOG) {
				controls[j++] = *bii.pShortVal >> 8;
				controls[j++] = *bii.pShortVal & 0xFF;
			} else {
				controls[j++] = *bii.pVal;
			}
		}
	}

	// DIP switches
	for (i = 0; i < DIPInputs; i++, j++) {
		BurnDrvGetInputInfo(&bii, i + DIPOffset);
		controls[j] = *bii.pVal;
	}

	// k has the size of all inputs for one player
	k = j + 1;

	// Send the control block to the Kaillera DLL & retrieve all controls
	if (kailleraModifyPlayValues(controls, k) == -1) {
		kNetGame = 0;
		kDropped = 0x00; // FBA Combo - extra players input hack
		return 1;
	}

	// Decode Player 1 input block
	for (i = 0, j = 0; i < playerInputs[0]; i++, j++) {
		BurnDrvGetInputInfo(&bii, i + playerOffset[0]);
		if (bii.nType == BIT_DIGITAL) {
			if (controls[j >> 3] & (1 << (j & 7))) {
				*bii.pVal = 0x01;
			} else {
				*bii.pVal = 0x00;
			}
		}
	}
	for (i = 0; i < commonInputs; i++, j++) {
		BurnDrvGetInputInfo(&bii, i + commonOffset);
		if (controls[j >> 3] & (1 << (j & 7))) {
			*bii.pVal = 0x01;
		} else {
			*bii.pVal = 0x00;
		}
	}

	// Convert j to byte count
	j = (j + 7) >> 3;

	// Analog inputs
	for (i = 0; i < playerInputs[0]; i++) {
		BurnDrvGetInputInfo(&bii, i + DIPOffset);
		if (bii.nType & BIT_GROUP_ANALOG) {
			*bii.pShortVal = (controls[j] << 8) | controls[j + 1];
			j += 2;
		}
	}

	// DIP switches
	for (i = 0; i < DIPInputs; i++, j++) {
		BurnDrvGetInputInfo(&bii, i + DIPOffset);
		*bii.pVal = controls[j];
	}

	// Decode other player's input blocks
	for (int l = 1; l < MAXPLAYER; l++) {
		if (playerInputs[l]) {
			for (i = 0, j = k * (l << 3); i < playerInputs[l]; i++, j++) {
				BurnDrvGetInputInfo(&bii, i + playerOffset[l]);
				if (bii.nType == BIT_DIGITAL) {
					if (controls[j >> 3] & (1 << (j & 7))) {
						*bii.pVal = 0x01;
					} else {
						*bii.pVal = 0x00;
					}
				}
			}

			j += commonInputs;

			// Convert j to byte count
			j = (j + 7) >> 3;

			// Analog inputs/constants
			for (i = 0; i < playerInputs[l]; i++) {
				BurnDrvGetInputInfo(&bii, i + playerOffset[l]);
				if (bii.nType != BIT_DIGITAL) {
					if (bii.nType & BIT_GROUP_ANALOG) {
						*bii.pShortVal = (controls[j] << 8) | controls[j + 1];
						j += 2;
					}
				}
			}
		}
	}

	return 0;
}

static char* CreateKailleraList()
{
	int size = 256 * 1024;
	char* pList = new char[size];
	char* pName = pList;

	if (pList == NULL) {
		return NULL;
	}

	// Add chat option to the gamelist
	pName += sprintf(pName, "* Chat only");
	pName++;

	if (avOk) {
		// Add all the driver names to the list
		unsigned drvCount = BurnGetDrvCount();
		for (unsigned select = 0; select < drvCount; select++) {
			if (BurnDrvGetFlags() & BDF_GAME_WORKING && auditState->get(select) != AuditState::AUDIT_FAIL) {
				char* szDecoratedName = decorateKailleraGameName(select);

				if (pName + strlen(szDecoratedName) >= pList + size) {
					size <<= 1;
					char* pNewList = (char*)realloc(pList, size);
					if (pNewList == NULL) {
						return NULL;
					}
					pName -= (unsigned int)pList;
					pList = pNewList;
					pName += (unsigned int)pList;
				}
				pName += sprintf(pName, "%s", szDecoratedName);
				pName++;
			}
		}
	}

	*pName = '\0';
	pName++;

	return pList;
}

static int WINAPI gameCallback(char* game, int player, int numplayers)
{
	unsigned select = 0;
	unsigned drvCount = BurnGetDrvCount();
	for (select = 0; select < drvCount; select++) {
		char* szDecoratedName = decorateKailleraGameName(select);
		if (!strcmp(szDecoratedName, game)) {
			break;
		}
	}

	if (select >= drvCount) {
		kailleraEndGame();
		return 1;
	}

	kNetGame = 1;
	kDropped = 0xff << numplayers;		// FBA Combo - extra players input hack
	HWND active = GetActiveWindow();

	cheatsAllowed = false;				// Disable cheats during netplay
	audioInterface.stop();				// Stop while we load roms
	BurnerDrvInit(select, false);		// Init the game driver
	scrnInit();
	audioInterface.play();				// Restart sound
	videoInterface.init();
	SetFocus(hScrnWnd);

	runMessageLoop();

	BurnerDrvExit();
	FBAKailleraEnd();

	cheatsAllowed = true;				// reenable cheats netplay has ended

	SetFocus(active);
	return 0;
}

static void WINAPI kChatCallback(char* nick, char* text)
{
}

static void WINAPI kDropCallback(char* nick, int playernb)
{
	kDropped |= 1 << (playernb - 1);	// FBA Combo - extra players input hack
}

bool FBAKailleraInit()
{
	if (LoadLibrary_KailleraClient_DLL("kailleraclient.dll") == FALSE) {
		MessageBox(hScrnWnd, L"Couldn't find kailleraclient.dll", APP_TITLE, MB_OK | MB_ICONERROR);
		return false;
	}
	kailleraInit();

	// turn off autofire
#ifndef NO_AUTOFIRE
	autofire.enabled = false;
#endif

	return true;
}

void doNetGame()
{
	kailleraInfos ki;
	char tmpver[128];
	char* gameList;

	if (driverOkay) {
		BurnerDrvExit();
		scrnTitle();
	}
	menuSync(MENUT_ALL);

	_snprintf(tmpver, 128, "%ls v%.20ls", APP_TITLE, appBurnVer);

	gameList = CreateKailleraList();

	ki.appName = tmpver;
	ki.gameList = gameList;
	ki.gameCallback = &gameCallback;
	ki.chatReceivedCallback = &kChatCallback;
	ki.clientDroppedCallback = &kDropCallback;
	ki.moreInfosCallback = NULL;

	kailleraSetInfos(&ki);

	kailleraSelectServerDialog(NULL);

	delete [] gameList;

	kailleraShutdown(); // kaillera close
	FreeLibrary_KailleraClient_DLL();

	mediaReInit();
}

void FBAKailleraEnd()
{
	if (kNetGame) {
		kNetGame = 0;
		kailleraEndGame();
	}
}
